package spring.boot.server.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import spring.boot.server.codec.Invocation;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 客户端 Channel 管理器。提供两种功能：
 * 1. 客户端 Channel 的管理
 * 2. 向客户端 Channel 发送消息
 */
@Slf4j
@Component
public class NettyChannelManager {

    /**
     * Channel 对应的用户
     */
    private static final AttributeKey<String> CHANNEL_ATTR_KEY_USER = AttributeKey.newInstance("userId");

    /**
     * Channel 映射
     */
    private ConcurrentMap<ChannelId, Channel> channels = new ConcurrentHashMap<>();

    /**
     * 用户与 Channel 的映射。
     * 通过它，可以获取用户对应的 Channel。这样，我们可以向指定用户发送消息。
     */
    private ConcurrentMap<String, Channel> userChannels = new ConcurrentHashMap<>();

    /**
     * 添加 Channel 到 channels 中
     */
    public void add(Channel channel) {
        channels.put(channel.id(), channel);
        log.info("Netty服务端添加一个连接[{}]到channels中", channel.id());
    }

    /**
     * 添加指定用户到 userChannels 中
     */
    public void addUser(Channel channel, String userId) {
        Channel existChannel = channels.get(channel.id());
        if (existChannel == null) {
            log.error("Netty服务端添加指定用户到userChannels中,连接[{}]不存在", channel.id());
            return;
        }
        // 设置属性
        channel.attr(CHANNEL_ATTR_KEY_USER).set(userId);
        // 添加到 userChannels
        userChannels.put(userId, channel);
    }

    /**
     * 将 Channel 从 channels 和 userChannels 中移除
     */
    public void remove(Channel channel) {
        // 移除 channels
        channels.remove(channel.id());
        // 移除 userChannels
        if (channel.hasAttr(CHANNEL_ATTR_KEY_USER)) {
            userChannels.remove(channel.attr(CHANNEL_ATTR_KEY_USER).get());
        }
        log.info("Netty服务端将Channel从channels和userChannels中移除,一个连接[{}]离开", channel.id());
    }

    /**
     * 向指定用户发送消息
     */
    public void send(String toUserId, Invocation invocation) {
        // 获得用户对应的 Channel
        Channel channel = userChannels.get(toUserId);
        if (channel == null) {
            log.error("Netty服务端向指定用户发送消息,连接不存在,toUserId=[{}]", toUserId);
            return;
        }
        if (!channel.isActive()) {
            log.error("Netty服务端向指定用户发送消息,连接[{}]未激活,toUserId=[{}]", channel.id(), toUserId);
            return;
        }
        channel.writeAndFlush(invocation);
        log.info("Netty服务端向指定用户发送消息成功，toUserId=[{}],msg=[{}]", toUserId, invocation);
    }

    /**
     * 向所有用户发送消息
     */
    public void sendAll(Invocation invocation) {
        for (Channel channel : channels.values()) {
            if (!channel.isActive()) {
                log.error("Netty服务端向所有用户发送消息,连接[{}]未激活", channel.id());
                return;
            }
            channel.writeAndFlush(invocation);
            log.info("Netty服务端群发消息，channelId=[{}],msg=[{}]", channel.id(), invocation);
        }
    }

}
